#include "stdafx.h"

#include "pjpro.h"

#ifdef USA_SRTP

inline int hex_char_to_nibble(char c) {
  switch(c) {
  case ('0'): return 0x0;
  case ('1'): return 0x1;
  case ('2'): return 0x2;
  case ('3'): return 0x3;
  case ('4'): return 0x4;
  case ('5'): return 0x5;
  case ('6'): return 0x6;
  case ('7'): return 0x7;
  case ('8'): return 0x8;
  case ('9'): return 0x9;
  case ('a'): return 0xa;
  case ('A'): return 0xa;
  case ('b'): return 0xb;
  case ('B'): return 0xb;
  case ('c'): return 0xc;
  case ('C'): return 0xc;
  case ('d'): return 0xd;
  case ('D'): return 0xd;
  case ('e'): return 0xe;
  case ('E'): return 0xe;
  case ('f'): return 0xf;
  case ('F'): return 0xf;
  default: break;   /* this flags an error */
  }
  return -1;
}

int hex_string_to_octet_string(char *raw, char *hex, int len) {
  char x;
  int tmp;
  int hex_len;

  hex_len = 0;
  while (hex_len < len) {
    tmp = hex_char_to_nibble(hex[0]);
    if (tmp == -1)
      return hex_len;
    x = (tmp << 4);
    hex_len++;
    tmp = hex_char_to_nibble(hex[1]);
    if (tmp == -1)
      return hex_len;
    x |= (tmp & 0xff);
    hex_len++;
    *raw++ = x;
    hex += 2;
  }
  return hex_len;
}

#endif

// Variabili Globali
pj_caching_pool *cp = NULL;
pjmedia_endpt *med_endpt = NULL;
pj_pool_t *pool = NULL;
pjmedia_stream *stream = NULL;
pjmedia_snd_port *snd_port = NULL;
pjmedia_stream_info *info = NULL;
pjmedia_codec_param *codec_param = NULL;
pjmedia_port *stream_port= NULL;
pjmedia_transport *transport = NULL;


int start(char * ipad, char * porta, char * codec, char *key_TX, char *key_RX){
	
	// Ricavo i parametri!!!
	pj_str_t ip = pj_str(ipad);
	pj_uint16_t port = (pj_uint16_t) atoi(porta);
	char *codec_id = codec;

	printf("\nPhone.exe\n---Ip:    %s\n---Porta: %d\n---Codec: %s\n",ip.ptr,port,codec_id);

	// Definizioni
	pj_status_t status; 
	pj_uint16_t local_port;
	pjmedia_dir dir = PJMEDIA_DIR_ENCODING_DECODING;
	pj_sockaddr_in remote_addr;
	pj_bzero(&remote_addr, sizeof(remote_addr));
	const pjmedia_codec_info *codec_info=NULL;
	cp = new pj_caching_pool();
	info = new pjmedia_stream_info();
	pj_bzero(info, sizeof(pjmedia_stream_info));
	codec_param = new pjmedia_codec_param();

#ifdef USA_SRTP

	/* SRTP variables */
    pj_bool_t use_srtp = PJ_TRUE;
    char tmp_tx_key[64];
    char tmp_rx_key[64];
    pj_str_t  srtp_tx_key = {NULL, 0};
    pj_str_t  srtp_rx_key = {NULL, 0};
    pj_str_t  srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
    int	tmp_key_len;

	tmp_key_len = hex_string_to_octet_string(tmp_tx_key, key_TX, strlen(key_TX));
	pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);

	tmp_key_len = hex_string_to_octet_string(tmp_rx_key, key_RX, strlen(key_RX));
	pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);

	pjmedia_transport *srtp_tp = NULL;

#endif


	// Init PJLIB
    status = pj_init();
	if (status != PJ_SUCCESS){
		printf("ERRORE! pj_init()\n");
		return 1;
	}

	// Controllo ip
	status = pj_sockaddr_in_init(&remote_addr, &ip, port);
	if (status != PJ_SUCCESS) {
	    printf("ERRORE! Indirizzo ip o porta non valida\n");
		return 2;
	}
	local_port=port;

	/* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(cp, &pj_pool_factory_default_policy, 0);

    /* Initialize media endpoint. */
    status = pjmedia_endpt_create(&cp->factory, NULL, 1, &med_endpt);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 1\n");
		return 1;
	}

    /* Create memory pool for application purpose */
    pool = pj_pool_create( &cp->factory,"app",4000,4000,NULL);

	/* Register all supported codecs */
#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
    status = pjmedia_codec_g711_init(med_endpt);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 2\n");
		return 1;
	}
#endif

#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
    status = pjmedia_codec_gsm_init(med_endpt);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 3\n");
		return 1;
	}
#endif

#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
    status = pjmedia_codec_speex_init(med_endpt, 0, -1, -1);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 4\n");
		return 1;
	}
#endif

#if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
    status = pjmedia_codec_g722_init(med_endpt);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 5\n");
		return 1;
	}
#endif

#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
    status = pjmedia_codec_l16_init(med_endpt, 0);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 6\n");
		return 1;
	}
#endif

	 /* Find which codec to use. */
    if (codec_id) {
		unsigned count = 1;
		pj_str_t str_codec_id = pj_str(codec_id);
		pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
		status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,&str_codec_id, &count,&codec_info, NULL);
		if (status != PJ_SUCCESS) {
			printf("ERRORE! Impossibile trovare il codec %s\n", codec_id);
			return 3;
		}
    } else {
		/* Default to pcmu */
		pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),0, &codec_info);
    }

	/* Reset stream info. */
    pj_bzero(info, sizeof(pjmedia_stream_info));

	/* Initialize stream info formats */
    info->type = PJMEDIA_TYPE_AUDIO;
    info->dir = dir;
    pj_memcpy(&info->fmt, codec_info, sizeof(pjmedia_codec_info));
    info->tx_pt = codec_info->pt;
    info->ssrc = pj_rand();

	/* Copy remote address */
    pj_memcpy(&info->rem_addr, &remote_addr, sizeof(pj_sockaddr_in));

	/* Create media transport */
    status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,0, &transport);
	if (status != PJ_SUCCESS){
		printf("ERRORE! 7\n");
		return 1;
	}

#ifdef USA_SRTP
    /* Check if SRTP enabled */
    if (use_srtp) {
	pjmedia_srtp_crypto tx_plc, rx_plc;

	status = pjmedia_transport_srtp_create(med_endpt, transport,NULL, &srtp_tp);
	if (status != PJ_SUCCESS){
		printf("ERRORE! 8\n");
		return 1;
	}

	pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
	pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));

	tx_plc.key = srtp_tx_key;
	tx_plc.name = srtp_crypto_suite;
	rx_plc.key = srtp_rx_key;
	rx_plc.name = srtp_crypto_suite;
	
	status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
	if (status != PJ_SUCCESS){
		printf("ERRORE! 9\n");
		return 1;
	}

	transport = srtp_tp;
    }
#endif

	status = pjmedia_stream_create( med_endpt, pool, info,transport,NULL,&stream);
    if (status != PJ_SUCCESS) {
		printf("ERRORE! Creazione stream fallita\n");
		pjmedia_transport_close(transport);
		return 1;
    }

	/* Get codec default param for info */
    status = pjmedia_codec_mgr_get_default_param(pjmedia_endpt_get_codec_mgr(med_endpt),codec_info,codec_param);
	if (status != PJ_SUCCESS){
		printf("ERRORE! 10\n");
		return 1;
	}

	/* Get the port interface of the stream */
    status = pjmedia_stream_get_port( stream, &stream_port);
    if (status != PJ_SUCCESS){
		printf("ERRORE! 11\n");
		return 1;
	}

	/* Create sound device port. */
	status = pjmedia_snd_port_create(pool, -1, -1,stream_port->info.clock_rate,stream_port->info.channel_count,
									stream_port->info.samples_per_frame,stream_port->info.bits_per_sample,0, &snd_port);
	if (status != PJ_SUCCESS) {
	    printf("ERRORE! Impossibile creare sound port");
	    return 1;
	}

	pjmedia_port *  	dn_port;
	pjmedia_echo_port_create(pool,stream_port,200,0,PJMEDIA_ECHO_SIMPLE,&dn_port);	 	

	/* Connect sound port to stream */
	//status = pjmedia_snd_port_connect( snd_port, stream_port );
	status = pjmedia_snd_port_connect( snd_port, dn_port );
	if (status != PJ_SUCCESS){
		printf("ERRORE! 12\n");
		return 1;
	}

    /* Start streaming */
    pjmedia_stream_start(stream);


    /* Done */
	printf("Streaming ATTIVO!!!\n");

	return 0;
}

int stop(){

	/* Destroy sound device */
    if (snd_port!=NULL) {
		pjmedia_snd_port_destroy( snd_port );
    }

    /* Destroy stream */
    if (stream!=NULL) {
		pjmedia_transport *tp;
		tp = pjmedia_stream_get_transport(stream);
		pjmedia_stream_destroy(stream);	
		pjmedia_transport_close(tp);
    }

#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
    pjmedia_codec_g711_deinit();
#endif

#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC!=0
    pjmedia_codec_gsm_deinit();
#endif

#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0
    pjmedia_codec_speex_deinit();
#endif

#if defined(PJMEDIA_HAS_G722_CODEC) && PJMEDIA_HAS_G722_CODEC!=0
    pjmedia_codec_g722_deinit();
#endif

#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
    pjmedia_codec_l16_deinit();
#endif

    /* Release application pool */
	if(pool!=NULL)
		pj_pool_release( pool );

    /* Destroy media endpoint. */
	if(med_endpt!=NULL)
		pjmedia_endpt_destroy( med_endpt );

    /* Destroy pool factory */
	if(cp!=NULL)
		pj_caching_pool_destroy( cp );	

    /* Shutdown PJLIB */
    pj_shutdown();

	/* Cancello e pulisco */
	delete med_endpt;
	delete pool;
	delete stream;
	delete snd_port;
	delete info;
	delete codec_param;
	delete stream_port;
	delete transport;
	cp = NULL;
	med_endpt = NULL;
	pool = NULL;
	stream = NULL;
	snd_port = NULL;
	info = NULL;
	codec_param = NULL;
	stream_port= NULL;
	transport = NULL;

	printf("PJMedia Terminata!!!\n");
	return 0;
}

static const char *good_number(char *buf, pj_int32_t val)
{
    if (val < 1000) {
	pj_ansi_sprintf(buf, "%d", val);
    } else if (val < 1000000) {
	pj_ansi_sprintf(buf, "%d.%dK", 
			val / 1000,
			(val % 1000) / 100);
    } else {
	pj_ansi_sprintf(buf, "%d.%02dM", 
			val / 1000000,
			(val % 1000000) / 10000);
    }

    return buf;
}


#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
    do { \
	if (samples <= 4294) \
	    usec = samples * 1000000 / clock_rate; \
	else { \
	    usec = samples * 1000 / clock_rate; \
	    usec *= 1000; \
	} \
    } while(0)

#define PRINT_VOIP_MTC_VAL(s, v) \
    if (v == 127) \
	sprintf(s, "(na)"); \
    else \
	sprintf(s, "%d", v)


/*
 * Print stream statistics
 */

void stampa(){
	print_stream_stat(stream,codec_param);
}

static void print_stream_stat(pjmedia_stream *stream,
			      const pjmedia_codec_param *codec_param)
{
    char duration[80], last_update[80];
    char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
    pjmedia_port *port;
    pjmedia_rtcp_stat stat;
    pj_time_val now;


    pj_gettimeofday(&now);
    pjmedia_stream_get_stat(stream, &stat);
    pjmedia_stream_get_port(stream, &port);

    puts("Stream statistics:");

    /* Print duration */
    PJ_TIME_VAL_SUB(now, stat.start);
    sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
	    now.sec / 3600,
	    (now.sec % 3600) / 60,
	    (now.sec % 60),
	    now.msec);


    printf(" Info: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
   	(int)port->info.encoding_name.slen,
	port->info.encoding_name.ptr,
	port->info.clock_rate,
	port->info.samples_per_frame * 1000 / port->info.clock_rate,
	good_number(bps, (codec_param->info.avg_bps+7)/8),
	good_number(ipbps, ((codec_param->info.avg_bps+7)/8) + 
			   (40 * 1000 /
			    codec_param->setting.frm_per_pkt /
			    codec_param->info.frm_ptime)));

    if (stat.rx.update_cnt == 0)
	strcpy(last_update, "never");
    else {
	pj_gettimeofday(&now);
	PJ_TIME_VAL_SUB(now, stat.rx.update);
	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		now.sec / 3600,
		(now.sec % 3600) / 60,
		now.sec % 60,
		now.msec);
    }

    printf(" RX stat last update: %s\n"
	   "    total %s packets %sB received (%sB +IP hdr)%s\n"
	   "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
	   "          (msec)    min     avg     max     last    dev\n"
	   "    loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
	   "    jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
	   last_update,
	   good_number(packets, stat.rx.pkt),
	   good_number(bytes, stat.rx.bytes),
	   good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
	   "",
	   stat.rx.loss,
	   stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
	   stat.rx.dup, 
	   stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
	   stat.rx.reorder, 
	   stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
	   "",
	   stat.rx.loss_period.min / 1000.0, 
	   stat.rx.loss_period.mean / 1000.0, 
	   stat.rx.loss_period.max / 1000.0,
	   stat.rx.loss_period.last / 1000.0,
	   pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
	   "",
	   stat.rx.jitter.min / 1000.0,
	   stat.rx.jitter.mean / 1000.0,
	   stat.rx.jitter.max / 1000.0,
	   stat.rx.jitter.last / 1000.0,
	   pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
	   ""
	   );


    if (stat.tx.update_cnt == 0)
	strcpy(last_update, "never");
    else {
	pj_gettimeofday(&now);
	PJ_TIME_VAL_SUB(now, stat.tx.update);
	sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		now.sec / 3600,
		(now.sec % 3600) / 60,
		now.sec % 60,
		now.msec);
    }

    printf(" TX stat last update: %s\n"
	   "    total %s packets %sB sent (%sB +IP hdr)%s\n"
	   "    pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
	   "          (msec)    min     avg     max     last    dev\n"
	   "    loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
	   "    jitter     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
	   last_update,
	   good_number(packets, stat.tx.pkt),
	   good_number(bytes, stat.tx.bytes),
	   good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
	   "",
	   stat.tx.loss,
	   stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
	   stat.tx.dup, 
	   stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
	   stat.tx.reorder, 
	   stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
	   "",
	   stat.tx.loss_period.min / 1000.0, 
	   stat.tx.loss_period.mean / 1000.0, 
	   stat.tx.loss_period.max / 1000.0,
	   stat.tx.loss_period.last / 1000.0,
	   pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
	   "",
	   stat.tx.jitter.min / 1000.0,
	   stat.tx.jitter.mean / 1000.0,
	   stat.tx.jitter.max / 1000.0,
	   stat.tx.jitter.last / 1000.0,
	   pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
	   ""
	   );


    printf(" RTT delay     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", 
	   stat.rtt.min / 1000.0,
	   stat.rtt.mean / 1000.0,
	   stat.rtt.max / 1000.0,
	   stat.rtt.last / 1000.0,
	   pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
	   ""
	   );

#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    /* RTCP XR Reports */
    do {
	char loss[16], dup[16];
	char jitter[80];
	char toh[80];
	char plc[16], jba[16], jbr[16];
	char signal_lvl[16], noise_lvl[16], rerl[16];
	char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
	pjmedia_rtcp_xr_stat xr_stat;

	if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
	    break;

	puts("\nExtended reports:");

	/* Statistics Summary */
	puts(" Statistics Summary");

	if (xr_stat.rx.stat_sum.l)
	    sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
	else
	    sprintf(loss, "(na)");

	if (xr_stat.rx.stat_sum.d)
	    sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
	else
	    sprintf(dup, "(na)");

	if (xr_stat.rx.stat_sum.j) {
	    unsigned jmin, jmax, jmean, jdev;

	    SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jdev, 
			   pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
			   port->info.clock_rate);
	    sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
		    jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
	} else
	    sprintf(jitter, "(report not available)");

	if (xr_stat.rx.stat_sum.t) {
	    sprintf(toh, "%11d %11d %11d %11d", 
		    xr_stat.rx.stat_sum.toh.min,
		    xr_stat.rx.stat_sum.toh.mean,
		    xr_stat.rx.stat_sum.toh.max,
		    pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
	} else
	    sprintf(toh, "(report not available)");

	if (xr_stat.rx.stat_sum.update.sec == 0)
	    strcpy(last_update, "never");
	else {
	    pj_gettimeofday(&now);
	    PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		    now.sec / 3600,
		    (now.sec % 3600) / 60,
		    now.sec % 60,
		    now.msec);
	}

	printf(" RX last update: %s\n"
	       "    begin seq=%d, end seq=%d%s\n"
	       "    pkt loss=%s, dup=%s%s\n"
	       "          (msec)    min     avg     max     dev\n"
	       "    jitter     : %s\n"
	       "    toh        : %s\n",
	       last_update,
	       xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
	       "",
	       loss, dup,
	       "",
	       jitter,
	       toh
	       );

	if (xr_stat.tx.stat_sum.l)
	    sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
	else
	    sprintf(loss, "(na)");

	if (xr_stat.tx.stat_sum.d)
	    sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
	else
	    sprintf(dup, "(na)");

	if (xr_stat.tx.stat_sum.j) {
	    unsigned jmin, jmax, jmean, jdev;

	    SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, 
			    port->info.clock_rate);
	    SAMPLES_TO_USEC(jdev, 
			   pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
			   port->info.clock_rate);
	    sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", 
		    jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
	} else
	    sprintf(jitter, "(report not available)");

	if (xr_stat.tx.stat_sum.t) {
	    sprintf(toh, "%11d %11d %11d %11d", 
		    xr_stat.tx.stat_sum.toh.min,
		    xr_stat.tx.stat_sum.toh.mean,
		    xr_stat.tx.stat_sum.toh.max,
		    pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
	} else
	    sprintf(toh,    "(report not available)");

	if (xr_stat.tx.stat_sum.update.sec == 0)
	    strcpy(last_update, "never");
	else {
	    pj_gettimeofday(&now);
	    PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		    now.sec / 3600,
		    (now.sec % 3600) / 60,
		    now.sec % 60,
		    now.msec);
	}

	printf(" TX last update: %s\n"
	       "    begin seq=%d, end seq=%d%s\n"
	       "    pkt loss=%s, dup=%s%s\n"
	       "          (msec)    min     avg     max     dev\n"
	       "    jitter     : %s\n"
	       "    toh        : %s\n",
	       last_update,
	       xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
	       "",
	       loss, dup,
	       "",
	       jitter,
	       toh
	       );

	/* VoIP Metrics */
	puts(" VoIP Metrics");

	PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
	PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
	PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
	PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
	PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
	PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
	PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);

	switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
	    case PJMEDIA_RTCP_XR_PLC_DIS:
		sprintf(plc, "DISABLED");
		break;
	    case PJMEDIA_RTCP_XR_PLC_ENH:
		sprintf(plc, "ENHANCED");
		break;
	    case PJMEDIA_RTCP_XR_PLC_STD:
		sprintf(plc, "STANDARD");
		break;
	    case PJMEDIA_RTCP_XR_PLC_UNK:
	    default:
		sprintf(plc, "UNKNOWN");
		break;
	}

	switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
	    case PJMEDIA_RTCP_XR_JB_FIXED:
		sprintf(jba, "FIXED");
		break;
	    case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
		sprintf(jba, "ADAPTIVE");
		break;
	    default:
		sprintf(jba, "UNKNOWN");
		break;
	}

	sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);

	if (xr_stat.rx.voip_mtc.update.sec == 0)
	    strcpy(last_update, "never");
	else {
	    pj_gettimeofday(&now);
	    PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		    now.sec / 3600,
		    (now.sec % 3600) / 60,
		    now.sec % 60,
		    now.msec);
	}

	printf(" RX last update: %s\n"
	       "    packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
	       "    burst      : density=%d (%.2f%%), duration=%d%s\n"
	       "    gap        : density=%d (%.2f%%), duration=%d%s\n"
	       "    delay      : round trip=%d%s, end system=%d%s\n"
	       "    level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
	       "    quality    : R factor=%s, ext R factor=%s\n"
	       "                 MOS LQ=%s, MOS CQ=%s\n"
	       "    config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
	       "    JB delay   : cur=%d%s, max=%d%s, abs max=%d%s\n",
	       last_update,
	       /* pakcets */
	       xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
	       xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
	       /* burst */
	       xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
	       xr_stat.rx.voip_mtc.burst_dur, "ms",
	       /* gap */
	       xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
	       xr_stat.rx.voip_mtc.gap_dur, "ms",
	       /* delay */
	       xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
	       xr_stat.rx.voip_mtc.end_sys_delay, "ms",
	       /* level */
	       signal_lvl, "dB",
	       noise_lvl, "dB",
	       rerl, "",
	       /* quality */
	       r_factor, ext_r_factor, mos_lq, mos_cq,
	       /* config */
	       plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
	       /* JB delay */
	       xr_stat.rx.voip_mtc.jb_nom, "ms",
	       xr_stat.rx.voip_mtc.jb_max, "ms",
	       xr_stat.rx.voip_mtc.jb_abs_max, "ms"
	       );

	PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
	PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
	PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
	PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
	PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
	PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
	PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);

	switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
	    case PJMEDIA_RTCP_XR_PLC_DIS:
		sprintf(plc, "DISABLED");
		break;
	    case PJMEDIA_RTCP_XR_PLC_ENH:
		sprintf(plc, "ENHANCED");
		break;
	    case PJMEDIA_RTCP_XR_PLC_STD:
		sprintf(plc, "STANDARD");
		break;
	    case PJMEDIA_RTCP_XR_PLC_UNK:
	    default:
		sprintf(plc, "unknown");
		break;
	}

	switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
	    case PJMEDIA_RTCP_XR_JB_FIXED:
		sprintf(jba, "FIXED");
		break;
	    case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
		sprintf(jba, "ADAPTIVE");
		break;
	    default:
		sprintf(jba, "unknown");
		break;
	}

	sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);

	if (xr_stat.tx.voip_mtc.update.sec == 0)
	    strcpy(last_update, "never");
	else {
	    pj_gettimeofday(&now);
	    PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
		    now.sec / 3600,
		    (now.sec % 3600) / 60,
		    now.sec % 60,
		    now.msec);
	}

	printf(" TX last update: %s\n"
	       "    packets    : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
	       "    burst      : density=%d (%.2f%%), duration=%d%s\n"
	       "    gap        : density=%d (%.2f%%), duration=%d%s\n"
	       "    delay      : round trip=%d%s, end system=%d%s\n"
	       "    level      : signal=%s%s, noise=%s%s, RERL=%s%s\n"
	       "    quality    : R factor=%s, ext R factor=%s\n"
	       "                 MOS LQ=%s, MOS CQ=%s\n"
	       "    config     : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
	       "    JB delay   : cur=%d%s, max=%d%s, abs max=%d%s\n",
	       last_update,
	       /* pakcets */
	       xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
	       xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
	       /* burst */
	       xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
	       xr_stat.tx.voip_mtc.burst_dur, "ms",
	       /* gap */
	       xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
	       xr_stat.tx.voip_mtc.gap_dur, "ms",
	       /* delay */
	       xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
	       xr_stat.tx.voip_mtc.end_sys_delay, "ms",
	       /* level */
	       signal_lvl, "dB",
	       noise_lvl, "dB",
	       rerl, "",
	       /* quality */
	       r_factor, ext_r_factor, mos_lq, mos_cq,
	       /* config */
	       plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
	       /* JB delay */
	       xr_stat.tx.voip_mtc.jb_nom, "ms",
	       xr_stat.tx.voip_mtc.jb_max, "ms",
	       xr_stat.tx.voip_mtc.jb_abs_max, "ms"
	       );


	/* RTT delay (by receiver side) */
	printf("          (msec)    min     avg     max     last    dev\n");
	printf(" RTT delay     : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", 
	       xr_stat.rtt.min / 1000.0,
	       xr_stat.rtt.mean / 1000.0,
	       xr_stat.rtt.max / 1000.0,
	       xr_stat.rtt.last / 1000.0,
	       pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
	       ""
	       );
    } while (0);
#endif /* PJMEDIA_HAS_RTCP_XR */

}
